package se.webbzon.oschi01.sound;

import java.io.IOException;
import java.io.InputStream;
import java.net.URL;

import se.webbzon.boltzmann.debug.Debug;

import javazoom.jl.decoder.JavaLayerException;
import javazoom.jl.player.FactoryRegistry;
import javazoom.jl.player.Player;

public class MP3Sound implements FiniteSound {
	
	private final MP3Source source;
	
	private final PlaybackThread playbackThread;
	
	private InputStream is;
	
	private Player player;
	
	private boolean looping;
	
	/*============================================================
	Constructors
	============================================================*/
	
	public MP3Sound(URL url) throws JavaLayerException, IOException {
		this(new MP3Source(url));
	}
	
	public MP3Sound(MP3Sound sound) throws JavaLayerException, IOException {
		this(sound.source);
	}
	
	public MP3Sound(MP3Source source) throws JavaLayerException, IOException {
		this.source = source;
		this.is = source.getURL().openStream();
		this.player = new Player(is,FactoryRegistry.systemRegistry().createAudioDevice());
		this.playbackThread = new PlaybackThread();
		looping = false;
	}
	
	/*============================================================
	Public Methods
	============================================================*/

	@Override public void play() {
		playbackThread.setPause(false);
		if (!playbackThread.isAlive())
			playbackThread.start();
	}
	
	@Override public boolean isPlaying() {
		synchronized (playbackThread) {
			if (playbackThread.isPaused() || playbackThread.isStopped())
				return false;
			else
				return true;
		}
	}

	@Override public void pause() {
		playbackThread.setPause(true);
	}
	
	@Override public void stop() {
		playbackThread.setStopped(true);
	}
	
	@Override public synchronized void setLooping(boolean looping) {
		this.looping = looping;
	}

	@Override public synchronized boolean isLooping() {
		return looping;
	}
	
	@Override public long length() {
		return -1;
	}

	@Override public long pos() {
		return -1;
	}

	@Override public boolean seek(long time) {
		return false;
	}
	
	/*============================================================
	Private Methods
	============================================================*/
	
	/** Returns the player of this MP3Sound. **/
	private synchronized Player getPlayer() {
		return player;
	}
	
	/** Closes the player and the input stream for this MP3Sound.**/
	private synchronized void close() throws IOException {
		player.close();
		is.close();
	}
	
	/** Closes the current player and input stream and restarts the
	 * MP3Sound from the beginning by opening a new stream from the url. **/
	private synchronized void restart() throws IOException, JavaLayerException {
		close();
		this.is = source.getURL().openStream();
		this.player = new Player(is,FactoryRegistry.systemRegistry().createAudioDevice());
	}
	
	/*============================================================
	Private Classes
	============================================================*/
	
	private final class PlaybackThread extends Thread {
		
		private static final String threadName = "MP3 Playback Thread";
		
		private boolean stopped, paused;
		
		public PlaybackThread() {
			super(threadName);
			stopped = false;
			paused = false;
		}

		@Override public void run() {
			try {
				while (!isStopped()) {
					Player player = getPlayer();
					if (player.isComplete()) {
						if (isLooping())
							restart();
						else {
							close();
							break;
						}
					} if (isPaused()) try {
						// Do nothing while paused
						Thread.sleep(10);
					} catch (InterruptedException e) {
						Debug.report(e);
						break;
					} else {
						// Play sound
						player.play(1);
					} 	
				}
				
			} catch (IOException | JavaLayerException e) {
				Debug.report(e);
			}
			setStopped(true);
		}
		
		public synchronized boolean isPaused() {
			return paused;
		}
		
		public synchronized void setPause(boolean paused) {
			this.paused = paused;
		}
		
		public synchronized boolean isStopped() {
			return stopped;
		}
		
		public synchronized void setStopped(boolean stopped) {
			this.stopped = stopped;
		}
		
	}

}
